{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "b5fc3f67",
   "metadata": {},
   "source": [
    "# Fine-Tuning LLaMA with LoRA for Math Word Problems\n",
    "\n",
    "This notebook demonstrates how to fine-tune the LLaMA-3.2-1B model using LoRA (Low-Rank Adaptation) on the Orca Math Word Problems dataset. We support multiple modes (Torch, 8-bit, 16-bit) and include evaluation metrics like perplexity.\n",
    "\n",
    "## Setup and Imports"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "eb91ac87",
   "metadata": {},
   "outputs": [],
   "source": [
    "import math\n",
    "import os\n",
    "import random\n",
    "import shutil\n",
    "from pathlib import Path\n",
    "\n",
    "import matplotlib.pyplot as plt\n",
    "import numpy as np\n",
    "import torch\n",
    "import torch.nn.functional as F\n",
    "from datasets import load_dataset\n",
    "from peft import LoraConfig, get_peft_model\n",
    "from torch.utils.data import DataLoader\n",
    "from tqdm import tqdm\n",
    "from transformers import AutoModelForCausalLM, AutoTokenizer, Trainer, TrainingArguments\n",
    "from utils_lora import generate_and_print\n",
    "\n",
    "from concrete.ml.torch.lora import LoraTrainer"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "36db3207",
   "metadata": {},
   "source": [
    "## Configuration Parameters\n",
    "\n",
    "Define the training mode and other parameters here. Modify these values as needed before running the notebook."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "3bf52256",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Mode: 7bit, Eval Steps: 100, Force CPU: False\n"
     ]
    }
   ],
   "source": [
    "# Training mode: 'torch', '7bit', or '16bit'\n",
    "MODE = \"7bit\"\n",
    "\n",
    "# Number of steps between evaluations\n",
    "EVAL_STEPS = 100\n",
    "\n",
    "# Set to True to force CPU\n",
    "FORCE_CPU = False\n",
    "\n",
    "# File paths based on mode\n",
    "mode_str = MODE\n",
    "EVAL_RESPONSES_FILE = f\"eval_generated_responses_{mode_str}.txt\"\n",
    "TRAIN_LOG_FILE = f\"training_log_{mode_str}.txt\"\n",
    "SAVE_PATH = Path(f\"deployment/llama_lora_finetuned_{mode_str}\")\n",
    "\n",
    "print(f\"Mode: {MODE}, Eval Steps: {EVAL_STEPS}, Force CPU: {FORCE_CPU}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "67b7aca0",
   "metadata": {},
   "source": [
    "## Device and Seed Configuration\n",
    "\n",
    "Set up the device (CPU/GPU/MPS) and random seeds for reproducibility."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "f83416a9",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Using device: mps\n"
     ]
    }
   ],
   "source": [
    "def get_device():\n",
    "    if FORCE_CPU:\n",
    "        return \"cpu\"\n",
    "    if torch.cuda.is_available():\n",
    "        return \"cuda\"\n",
    "    if torch.backends.mps.is_available() and torch.backends.mps.is_built():\n",
    "        os.environ[\"PYTORCH_ENABLE_MPS_FALLBACK\"] = \"1\"\n",
    "        return \"mps\"\n",
    "    return \"cpu\"\n",
    "\n",
    "\n",
    "def set_seed(seed):\n",
    "    \"\"\"Set random seeds for reproducibility.\n",
    "\n",
    "    Args:\n",
    "        seed (int): The random seed to use\n",
    "    \"\"\"\n",
    "    random.seed(seed)\n",
    "    np.random.seed(seed)\n",
    "    torch.use_deterministic_algorithms(True, warn_only=True)\n",
    "    torch.manual_seed(seed)\n",
    "    if torch.cuda.is_available():\n",
    "        torch.cuda.manual_seed_all(seed)\n",
    "        torch.backends.cudnn.deterministic = True\n",
    "        torch.backends.cudnn.benchmark = False\n",
    "\n",
    "\n",
    "SEED = 0\n",
    "DEVICE = get_device()\n",
    "set_seed(SEED)\n",
    "print(f\"Using device: {DEVICE}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5a064520",
   "metadata": {},
   "source": [
    "## Load Model and Tokenizer\n",
    "\n",
    "Load the LLaMA model and tokenizer, and test the base model output."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "ab290ad8",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Prompt: When you multiply a number by 7, it becomes 98. What is that number?\n",
      "\n",
      "Response: I have a problem with my math. I know that 7 is the answer, but I can't figure out what number it is. I am\n",
      "\n"
     ]
    }
   ],
   "source": [
    "model_name = \"meta-llama/Llama-3.2-1B\"\n",
    "tokenizer = AutoTokenizer.from_pretrained(model_name)\n",
    "model = AutoModelForCausalLM.from_pretrained(model_name).to(DEVICE)\n",
    "\n",
    "if tokenizer.pad_token is None:\n",
    "    tokenizer.pad_token = tokenizer.eos_token\n",
    "model.config.pad_token_id = model.config.eos_token_id\n",
    "for p in model.parameters():\n",
    "    p.requires_grad = False\n",
    "\n",
    "PROMPT = \"When you multiply a number by 7, it becomes 98. What is that number?\\n\"\n",
    "_ = generate_and_print(PROMPT, model, tokenizer, seed=SEED)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0855c901",
   "metadata": {},
   "source": [
    "## LoRA Configuration\n",
    "\n",
    "Set up LoRA parameters and apply them to the model."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "e0e83963",
   "metadata": {},
   "outputs": [],
   "source": [
    "peft_config = LoraConfig(\n",
    "    r=8,\n",
    "    lora_alpha=32,\n",
    "    lora_dropout=0.01,\n",
    "    bias=\"none\",\n",
    "    task_type=\"CAUSAL_LM\",\n",
    "    target_modules=\"all-linear\",\n",
    ")\n",
    "peft_model = get_peft_model(model, peft_config)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b293bc07",
   "metadata": {},
   "source": [
    "## Dataset Preprocessing\n",
    "\n",
    "Load the dataset, filter by length, and preprocess for training."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "0373b27e",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...\n",
      "To disable this warning, you can either:\n",
      "\t- Avoid using `tokenizers` before the fork if possible\n",
      "\t- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "Length Distribution Statistics:\n",
      "Original dataset size: 200,035\n",
      "Filtered dataset size: 1,557\n",
      "Percentage kept: 0.8%\n",
      "\n",
      "Question lengths: \n",
      "  Min: 4, Max: 61\n",
      "  Mean: 24.7\n",
      "  Median: 25\n",
      "\n",
      "Answer lengths:\n",
      "  Min: 1, Max: 55\n",
      "  Mean: 23.5\n",
      "  Median: 23\n",
      "\n",
      "Total lengths (including newline):\n",
      "  Min: 6, Max: 64\n",
      "  Mean: 49.2\n",
      "  Median: 51\n",
      "\n",
      "Train samples: 1479, Test samples: 78\n"
     ]
    }
   ],
   "source": [
    "MAX_LENGTH = 64\n",
    "raw_dataset = load_dataset(\"microsoft/orca-math-word-problems-200k\", split=\"train\")\n",
    "\n",
    "\n",
    "def length_filter(example):\n",
    "    q_len = len(tokenizer(example[\"question\"], add_special_tokens=False)[\"input_ids\"])\n",
    "    a_len = len(tokenizer(example[\"answer\"], add_special_tokens=False)[\"input_ids\"])\n",
    "    return (q_len + a_len + 1) <= MAX_LENGTH\n",
    "\n",
    "\n",
    "filtered_dataset = raw_dataset.filter(length_filter)\n",
    "\n",
    "\n",
    "def get_lengths(example):\n",
    "    q_len = len(tokenizer(example[\"question\"], add_special_tokens=False)[\"input_ids\"])\n",
    "    a_len = len(tokenizer(example[\"answer\"], add_special_tokens=False)[\"input_ids\"])\n",
    "    total_len = q_len + a_len + 1\n",
    "    return {\"q_len\": q_len, \"a_len\": a_len, \"total_len\": total_len}\n",
    "\n",
    "\n",
    "lengths = filtered_dataset.map(get_lengths)\n",
    "q_lengths = [x[\"q_len\"] for x in lengths]\n",
    "a_lengths = [x[\"a_len\"] for x in lengths]\n",
    "total_lengths = [x[\"total_len\"] for x in lengths]\n",
    "\n",
    "print(\"\\nLength Distribution Statistics:\")\n",
    "print(f\"Original dataset size: {len(raw_dataset):,}\")\n",
    "print(f\"Filtered dataset size: {len(filtered_dataset):,}\")\n",
    "print(f\"Percentage kept: {100 * len(filtered_dataset)/len(raw_dataset):.1f}%\\n\")\n",
    "print(\"Question lengths: \")\n",
    "print(f\"  Min: {min(q_lengths)}, Max: {max(q_lengths)}\")\n",
    "print(f\"  Mean: {sum(q_lengths)/len(q_lengths):.1f}\")\n",
    "print(f\"  Median: {sorted(q_lengths)[len(q_lengths)//2]}\")\n",
    "print(\"\\nAnswer lengths:\")\n",
    "print(f\"  Min: {min(a_lengths)}, Max: {max(a_lengths)}\")\n",
    "print(f\"  Mean: {sum(a_lengths)/len(a_lengths):.1f}\")\n",
    "print(f\"  Median: {sorted(a_lengths)[len(a_lengths)//2]}\")\n",
    "print(\"\\nTotal lengths (including newline):\")\n",
    "print(f\"  Min: {min(total_lengths)}, Max: {max(total_lengths)}\")\n",
    "print(f\"  Mean: {sum(total_lengths)/len(total_lengths):.1f}\")\n",
    "print(f\"  Median: {sorted(total_lengths)[len(total_lengths)//2]}\\n\")\n",
    "\n",
    "\n",
    "def process_example(example):\n",
    "    \"\"\"Tokenize a question-answer pair and prepare labels for training.\n",
    "\n",
    "    Args:\n",
    "        example (dict): Dictionary with 'question' and 'answer' strings\n",
    "    Returns:\n",
    "        dict: Processed tokens with masked labels for the question portion\n",
    "    \"\"\"\n",
    "    question = example[\"question\"].strip()\n",
    "    answer = example[\"answer\"].strip()\n",
    "    tokens = tokenizer(\n",
    "        question + \"\\n\" + answer,\n",
    "        padding=\"max_length\",\n",
    "        truncation=True,\n",
    "        max_length=MAX_LENGTH,\n",
    "    )\n",
    "    question_length = len(tokenizer(question, add_special_tokens=False)[\"input_ids\"]) + 1\n",
    "    labels = tokens[\"input_ids\"].copy()\n",
    "    for i in range(question_length):\n",
    "        if i < len(labels):\n",
    "            labels[i] = -100\n",
    "    tokens[\"labels\"] = labels\n",
    "    return tokens\n",
    "\n",
    "\n",
    "tokenized_dataset = filtered_dataset.map(\n",
    "    process_example,\n",
    "    batched=False,\n",
    "    remove_columns=filtered_dataset.column_names,\n",
    ")\n",
    "\n",
    "tokenized = tokenized_dataset.train_test_split(test_size=0.05, seed=SEED, shuffle=True)\n",
    "train_dataset, test_dataset = tokenized[\"train\"], tokenized[\"test\"]\n",
    "print(f\"Train samples: {len(train_dataset)}, Test samples: {len(test_dataset)}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "59b530ff",
   "metadata": {},
   "source": [
    "## Data Collator\n",
    "\n",
    "Define a custom data collator to handle padding while preserving label masking."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "4b120a05",
   "metadata": {},
   "outputs": [],
   "source": [
    "class DataCollator:\n",
    "    def __init__(self, tokenizer):\n",
    "        self.pad_id = tokenizer.pad_token_id\n",
    "\n",
    "    def __call__(self, examples):\n",
    "        inputs, attention_masks, labels = [], [], []\n",
    "        max_length = max(len(example[\"input_ids\"]) for example in examples)\n",
    "        for example in examples:\n",
    "            inputs.append(example[\"input_ids\"])\n",
    "            attention_masks.append(example[\"attention_mask\"])\n",
    "            labels.append(example[\"labels\"])\n",
    "\n",
    "        def pad(sequences, value):\n",
    "            return [x + [value] * (max_length - len(x)) for x in sequences]\n",
    "\n",
    "        return {\n",
    "            \"input_ids\": torch.tensor(pad(inputs, self.pad_id)),\n",
    "            \"attention_mask\": torch.tensor(pad(attention_masks, 0)),\n",
    "            \"labels\": torch.tensor(pad(labels, -100)),\n",
    "        }\n",
    "\n",
    "\n",
    "collator = DataCollator(tokenizer)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cf397552",
   "metadata": {},
   "source": [
    "## Training Arguments\n",
    "\n",
    "Configure the training hyperparameters."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "2db068dc",
   "metadata": {},
   "outputs": [],
   "source": [
    "training_args = TrainingArguments(\n",
    "    output_dir=\"./checkpoints\",\n",
    "    num_train_epochs=1,\n",
    "    per_device_train_batch_size=1,\n",
    "    gradient_accumulation_steps=1,\n",
    "    save_total_limit=1,\n",
    "    use_cpu=True,\n",
    "    learning_rate=2e-4,\n",
    "    lr_scheduler_type=\"linear\",\n",
    "    seed=SEED,\n",
    "    data_seed=SEED,\n",
    "    warmup_steps=10,\n",
    "    weight_decay=0.01,\n",
    "    prediction_loss_only=True,\n",
    "    report_to=\"none\",\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b40a8346",
   "metadata": {},
   "source": [
    "## Loss and Evaluation Metrics\n",
    "\n",
    "Define the loss function and evaluation metric (perplexity)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "ee6eb07b",
   "metadata": {},
   "outputs": [],
   "source": [
    "def causal_lm_loss(logits, labels):\n",
    "    shift_logits = logits[..., :-1, :].contiguous()\n",
    "    shift_labels = labels[..., 1:].contiguous()\n",
    "    return F.cross_entropy(\n",
    "        shift_logits.view(-1, shift_logits.size(-1)),\n",
    "        shift_labels.view(-1),\n",
    "        ignore_index=-100,\n",
    "        reduction=\"mean\",\n",
    "    )\n",
    "\n",
    "\n",
    "def metric_fn(model, dataloader):\n",
    "    model.eval()\n",
    "    model.to(DEVICE)\n",
    "    total_loss, total_tokens, results = 0.0, 0, []\n",
    "    response = generate_and_print(PROMPT, model, tokenizer, seed=SEED)\n",
    "    if response:\n",
    "        results.append({\"prompt\": PROMPT, \"response\": response})\n",
    "\n",
    "    for batch in tqdm(dataloader, desc=\"Evaluating\", leave=False):\n",
    "        with torch.no_grad():\n",
    "            input_ids = batch[\"input_ids\"].to(DEVICE)\n",
    "            batch_labels = batch[\"labels\"].to(DEVICE)\n",
    "            attention_mask = batch[\"attention_mask\"].to(DEVICE)\n",
    "            outputs = model(input_ids, attention_mask=attention_mask).logits\n",
    "            valid = batch_labels[..., 1:] != -100\n",
    "            loss = F.cross_entropy(\n",
    "                outputs[..., :-1, :].contiguous().view(-1, outputs.size(-1)),\n",
    "                batch_labels[..., 1:].contiguous().view(-1),\n",
    "                ignore_index=-100,\n",
    "                reduction=\"sum\",\n",
    "            )\n",
    "        total_loss += loss.item()\n",
    "        total_tokens += valid.sum().item()\n",
    "    perplexity = math.exp(total_loss / total_tokens) if total_tokens > 0 else float(\"inf\")\n",
    "\n",
    "    with open(EVAL_RESPONSES_FILE, \"a\", encoding=\"utf-8\") as f:\n",
    "        f.write(f\"Perplexity: {perplexity:.2f}\\n\")\n",
    "        for i, r in enumerate(results):\n",
    "            f.write(\n",
    "                f\"== Generation {i+1} ==\\nPrompt:\\n{r['prompt']}\\n\\nResponse:\\n{r['response']}\\n\"\n",
    "            )\n",
    "            f.write(\"=\" * 40 + \"\\n\")\n",
    "\n",
    "    return {\"perplexity\": perplexity}"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1d167837",
   "metadata": {},
   "source": [
    "## Trainer Setup\n",
    "\n",
    "Initialize the Hugging Face Trainer to set up the optimizer and scheduler."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "0c8e7c86",
   "metadata": {},
   "outputs": [],
   "source": [
    "hf_trainer = Trainer(\n",
    "    model=peft_model,\n",
    "    args=training_args,\n",
    "    train_dataset=train_dataset,\n",
    "    data_collator=collator,\n",
    ")\n",
    "train_dl = hf_trainer.get_train_dataloader()\n",
    "hf_trainer.create_optimizer_and_scheduler(len(train_dl) * training_args.num_train_epochs)\n",
    "optimizer, lr_scheduler = hf_trainer.optimizer, hf_trainer.lr_scheduler"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f97c048d",
   "metadata": {},
   "source": [
    "## Calibration and Evaluation Data\n",
    "\n",
    "Prepare dummy calibration data and the evaluation DataLoader."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "face520f",
   "metadata": {},
   "outputs": [],
   "source": [
    "def get_inputset():\n",
    "    return {\n",
    "        \"input_ids\": torch.randint(0, tokenizer.vocab_size, (4, MAX_LENGTH)),\n",
    "        \"attention_mask\": torch.ones((4, MAX_LENGTH), dtype=torch.long),\n",
    "        \"labels\": torch.randint(0, tokenizer.vocab_size, (4, MAX_LENGTH)),\n",
    "    }\n",
    "\n",
    "\n",
    "eval_dl = DataLoader(test_dataset, batch_size=4, shuffle=False, collate_fn=collator)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f3afbe6e",
   "metadata": {},
   "source": [
    "## Model Initialization Function\n",
    "\n",
    "Add a function to initialize models with consistent weights for our comparison experiments."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "4de029b2",
   "metadata": {},
   "outputs": [],
   "source": [
    "def initialize_model(seed=SEED):\n",
    "    \"\"\"Initialize the model with consistent weights.\"\"\"\n",
    "    set_seed(seed)\n",
    "\n",
    "    model = AutoModelForCausalLM.from_pretrained(model_name).to(DEVICE)\n",
    "\n",
    "    if tokenizer.pad_token is None:\n",
    "        tokenizer.pad_token = tokenizer.eos_token\n",
    "    model.config.pad_token_id = model.config.eos_token_id\n",
    "    for p in model.parameters():\n",
    "        p.requires_grad = False\n",
    "\n",
    "    peft_config = LoraConfig(\n",
    "        r=8,\n",
    "        lora_alpha=32,\n",
    "        lora_dropout=0.01,\n",
    "        bias=\"none\",\n",
    "        task_type=\"CAUSAL_LM\",\n",
    "        target_modules=\"all-linear\",\n",
    "    )\n",
    "    peft_model = get_peft_model(model, peft_config)\n",
    "    peft_model = peft_model.to(DEVICE)\n",
    "    return peft_model\n",
    "\n",
    "\n",
    "def setup_trainer(model, steps=EVAL_STEPS, logging_steps=1):\n",
    "    \"\"\"Set up the trainer, optimizer and scheduler.\"\"\"\n",
    "    hf_trainer = Trainer(\n",
    "        model=model,\n",
    "        args=training_args,\n",
    "        train_dataset=train_dataset,\n",
    "        data_collator=collator,\n",
    "    )\n",
    "    train_dl = hf_trainer.get_train_dataloader()\n",
    "    hf_trainer.create_optimizer_and_scheduler(len(train_dl) * training_args.num_train_epochs)\n",
    "    optimizer, lr_scheduler = hf_trainer.optimizer, hf_trainer.lr_scheduler\n",
    "\n",
    "    lora_trainer = LoraTrainer(\n",
    "        model=model,\n",
    "        optimizer=optimizer,\n",
    "        loss_fn=causal_lm_loss,\n",
    "        lr_scheduler=lr_scheduler,\n",
    "        training_args=vars(training_args),\n",
    "        n_layers_to_skip_for_backprop=3,\n",
    "        eval_loader=eval_dl,\n",
    "        eval_metric_fn=metric_fn,\n",
    "        logging_steps=logging_steps,\n",
    "        eval_steps=steps,\n",
    "        train_log_path=TRAIN_LOG_FILE,\n",
    "    )\n",
    "\n",
    "    if MODE != \"torch\":\n",
    "        bits = 7 if MODE == \"7bit\" else 16\n",
    "        lora_trainer.compile(get_inputset(), n_bits=bits)\n",
    "\n",
    "    return lora_trainer, train_dl\n",
    "\n",
    "\n",
    "def extract_lora_weights(model):\n",
    "    \"\"\"Extract LoRA weights from model for comparison.\"\"\"\n",
    "    weights = {}\n",
    "    for name, param in model.named_parameters():\n",
    "        if \"lora_\" in name and param.requires_grad:\n",
    "            weights[name] = param.detach().cpu().numpy().copy()\n",
    "    return weights\n",
    "\n",
    "\n",
    "# Select only the first 5 batches for initial comparison\n",
    "def get_limited_batches(dataloader, num_batches=5):\n",
    "    \"\"\"Get a limited number of batches from dataloader.\"\"\"\n",
    "    limited_batches = []\n",
    "    for i, batch in enumerate(dataloader):\n",
    "        if i < num_batches:\n",
    "            limited_batches.append(batch)\n",
    "        else:\n",
    "            break\n",
    "    return limited_batches"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e6b8c6da",
   "metadata": {},
   "source": [
    "## Experiment: Comparing fhe=\"disable\" vs fhe=\"execute\"\n",
    "\n",
    "Train two identical models for 5 steps using the quantized clear model (disable mode) and compare against FHE fine-tuning (execute mode)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "6964b0b3",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "2025-03-25 15:18:36,880 - INFO - === Starting new training session ===\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "LoRA layers detected in the model.\n"
     ]
    }
   ],
   "source": [
    "# Original model trainer for reference\n",
    "lora_trainer = LoraTrainer(\n",
    "    model=peft_model,\n",
    "    optimizer=optimizer,\n",
    "    loss_fn=causal_lm_loss,\n",
    "    lr_scheduler=lr_scheduler,\n",
    "    training_args=vars(training_args),\n",
    "    n_layers_to_skip_for_backprop=3,\n",
    "    eval_loader=eval_dl,\n",
    "    eval_metric_fn=None,\n",
    "    logging_steps=1,\n",
    "    eval_steps=EVAL_STEPS,\n",
    "    train_log_path=TRAIN_LOG_FILE,\n",
    ")\n",
    "\n",
    "# Clear the existing file for our experiments\n",
    "with open(EVAL_RESPONSES_FILE, \"w\", encoding=\"utf-8\") as f:\n",
    "    f.write(\"=== Experiment: disable vs simulate ===\\n\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "e4bcf3fa",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Initializing first model (fhe=disable)...\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "2025-03-25 15:18:39,194 - INFO - === Starting new training session ===\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "LoRA layers detected in the model.\n"
     ]
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "fafc0c9c315a4675a566a53f6bd23f03",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Compiling FHE layers:   0%|          | 0/221 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "2025-03-25 15:20:24,975 - INFO - Compilation complete.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Evaluating pre-training model...\n",
      "Prompt: When you multiply a number by 7, it becomes 98. What is that number?\n",
      "\n",
      "Response: I have a problem with this question. I know that 7 is a factor of 98, but what is the number that is a factor of\n",
      "\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "                                                           "
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Pre-training perplexity: 37.06\n",
      "\n",
      "Training with fhe=disable for 5 steps...\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\r"
     ]
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "071663c209be4f9386a231c70d846f77",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Epoch 1:   0%|          | 0/5 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "2025-03-25 15:21:27,313 - INFO - Step 1: loss=2.163410, avg_loss=2.163410\n",
      "2025-03-25 15:21:27,374 - INFO - Average gradient magnitude: 0.764078\n",
      "2025-03-25 15:21:29,799 - INFO - Step 2: loss=3.122139, avg_loss=2.642774\n",
      "2025-03-25 15:21:29,842 - INFO - Average gradient magnitude: 1.383245\n",
      "2025-03-25 15:21:32,195 - INFO - Step 3: loss=4.424260, avg_loss=3.236603\n",
      "2025-03-25 15:21:32,240 - INFO - Average gradient magnitude: 2.932004\n",
      "2025-03-25 15:21:34,564 - INFO - Step 4: loss=0.981824, avg_loss=2.672908\n",
      "2025-03-25 15:21:34,607 - INFO - Average gradient magnitude: 0.219038\n",
      "2025-03-25 15:21:37,091 - INFO - Step 5: loss=0.893339, avg_loss=2.316994\n",
      "2025-03-25 15:21:37,138 - INFO - Average gradient magnitude: 0.286395\n",
      "2025-03-25 15:21:37,204 - INFO - Epoch 1 completed. Avg Loss: 2.316994, FHE Mode: disable\n",
      "2025-03-25 15:21:37,204 - INFO - Training completed. Final Avg Loss: 2.316994, FHE Mode: disable\n"
     ]
    }
   ],
   "source": [
    "print(\"Initializing first model (fhe=disable)...\")\n",
    "model_disable = initialize_model()\n",
    "initial_weights_disable = extract_lora_weights(model_disable)\n",
    "\n",
    "trainer_disable, train_dl = setup_trainer(model_disable)\n",
    "\n",
    "# Evaluate pre-training\n",
    "print(\"Evaluating pre-training model...\")\n",
    "model_disable.eval()\n",
    "pre_training_metrics = metric_fn(model_disable, eval_dl)\n",
    "print(f\"Pre-training perplexity: {pre_training_metrics['perplexity']:.2f}\")\n",
    "\n",
    "# Get limited batches for consistent training\n",
    "limited_batches = get_limited_batches(train_dl, 5)\n",
    "\n",
    "# Train with fhe=disable for 5 steps\n",
    "print(\"\\nTraining with fhe=disable for 5 steps...\")\n",
    "trainer_disable.train(limited_batches, fhe=\"disable\", device=DEVICE)\n",
    "losses_disable = trainer_disable.get_training_losses()\n",
    "\n",
    "# Store LoRA weights\n",
    "weights_disable = extract_lora_weights(model_disable)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "fd66e9c1",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "Initializing second model (fhe=execute)...\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "2025-03-25 15:24:17,754 - INFO - === Starting new training session ===\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "LoRA layers detected in the model.\n"
     ]
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "476dfe4b4a574e4eb83e138ef2c57fa3",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Compiling FHE layers:   0%|          | 0/221 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "2025-03-25 15:26:01,179 - INFO - Compilation complete.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Verifying initial weights are identical...\n",
      "Initial weights match between models.\n",
      "\n",
      "Training with fhe=execute for 5 steps...\n"
     ]
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "5f55f53bc5844ca18d16536fee5cd518",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Epoch 1:   0%|          | 0/5 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "2025-03-25 08:47:17,002 - INFO - Step 1: loss=2.198043, avg_loss=2.198043\n",
      "2025-03-25 08:47:17,018 - INFO - Average gradient magnitude: 10.796187\n",
      "2025-03-25 09:45:10,070 - INFO - Step 2: loss=3.195699, avg_loss=2.696871\n",
      "2025-03-25 09:45:10,087 - INFO - Average gradient magnitude: 14.245789\n",
      "2025-03-25 10:43:01,591 - INFO - Step 3: loss=5.219449, avg_loss=3.537730\n",
      "2025-03-25 10:43:01,608 - INFO - Average gradient magnitude: 22.919750\n",
      "2025-03-25 11:40:54,081 - INFO - Step 4: loss=0.987054, avg_loss=2.900061\n",
      "2025-03-25 11:40:54,097 - INFO - Average gradient magnitude: 3.477989\n",
      "2025-03-25 12:38:47,204 - INFO - Step 5: loss=0.822733, avg_loss=2.484596\n",
      "2025-03-25 12:38:47,222 - INFO - Average gradient magnitude: 7.610127\n",
      "2025-03-25 12:38:47,240 - INFO - Epoch 1 completed. Avg Loss: 2.484596, FHE Mode: execute\n",
      "2025-03-25 12:38:47,241 - INFO - Training completed. Final Avg Loss: 2.484596, FHE Mode: execute\n"
     ]
    }
   ],
   "source": [
    "print(\"\\nInitializing second model (fhe=execute)...\")\n",
    "model_execute = initialize_model()\n",
    "trainer_execute, _ = setup_trainer(model_execute)\n",
    "\n",
    "# Verify weights are the same before training\n",
    "print(\"Verifying initial weights are identical...\")\n",
    "initial_weights_execute = extract_lora_weights(model_execute)\n",
    "weights_match = True\n",
    "for name, weight in initial_weights_disable.items():\n",
    "    if not np.allclose(weight, initial_weights_execute[name]):\n",
    "        print(f\"Weight mismatch in {name}\")\n",
    "        weights_match = False\n",
    "if weights_match:\n",
    "    print(\"Initial weights match between models.\")\n",
    "\n",
    "# Train with fhe=execute for 5 steps\n",
    "print(\"\\nTraining with fhe=execute for 5 steps...\")\n",
    "trainer_execute.train(limited_batches, fhe=\"execute\", device=DEVICE)\n",
    "losses_execute = trainer_execute.get_training_losses()\n",
    "\n",
    "# Compare LoRA weights after training\n",
    "weights_execute = extract_lora_weights(model_execute)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "aef20b39",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAABKUAAAMWCAYAAAAgRDUeAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuNSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/xnp5ZAAAACXBIWXMAAA9hAAAPYQGoP6dpAADJ5ElEQVR4nOzdeZjV4//H8eeZmWbaS2lTSVoU0oJIJCqtIksUJTtF+Nq+llJESLYWIsousn4JZYnsQpEloRRFtvZtlvP74/5V0jZNM/M5M/N8XNdc53PW+3Wm6cyZ97nv9x2Lx+NxJEmSJEmSpHyUFHUASZIkSZIkFT0WpSRJkiRJkpTvLEpJkiRJkiQp31mUkiRJkiRJUr6zKCVJkiRJkqR8Z1FKkiRJkiRJ+c6ilCRJkiRJkvKdRSlJkiRJkiTlO4tSkiRJkiRJyncWpSRJkrRV8+bNIxaLMX78+KijSJKkQsailCRJynfjx48nFosxffr0qKNky4wZMzj11FOpWbMmaWlpVKhQgbZt2zJu3DgyMzOjjidJklQgpUQdQJIkKZGNHTuW8847jypVqtCrVy/q1avH8uXLeeONNzjzzDNZtGgRV199ddQx80ytWrVYvXo1xYoVizqKJEkqZCxKSZIkbcWHH37IeeedR4sWLZg0aRJlypTZcN3FF1/M9OnTmTVrVoQJ805GRgZZWVmkpqZSvHjxqONIkqRCyOV7kiQpYX3++ed07NiRsmXLUrp0adq0acOHH364yW3S09MZPHgw9erVo3jx4lSsWJFDDz2UKVOmbLjNr7/+yumnn06NGjVIS0ujWrVqHHPMMcybN2+b4w8ePJhYLMZjjz22SUFqvQMOOIA+ffpsOL9y5UouvfTSDcv89tprL2677Tbi8fgm94vFYlxwwQU8/fTT7L333pQoUYIWLVrw5ZdfAjBmzBjq1q1L8eLFad269WY5W7duzb777sunn37KIYccQokSJahduzb33nvvJrdbt24dAwcOZP/996dcuXKUKlWKww47jLfeemuT263vG3Xbbbdx5513UqdOHdLS0vj666+32FMqu9/P0aNHs88++5CWlsZuu+1Gv379WLJkyRafy9dff80RRxxByZIlqV69Orfeeus2/mUkSVJh4EwpSZKUkL766isOO+wwypYtyxVXXEGxYsUYM2YMrVu35u233+aggw4CYNCgQQwdOpSzzjqL5s2bs2zZMqZPn85nn31Gu3btADj++OP56quvuPDCC9ljjz1YvHgxU6ZMYf78+eyxxx5bHH/VqlW88cYbtGrVit133327eePxOF27duWtt97izDPPpEmTJrz22mtcfvnl/PLLL9xxxx2b3H7atGm8+OKL9OvXD4ChQ4fSpUsXrrjiCkaPHk3fvn35+++/ufXWWznjjDN48803N7n/33//TadOnejevTs9evTgqaee4vzzzyc1NZUzzjgDgGXLljF27Fh69OjB2WefzfLly3nggQdo3749H3/8MU2aNNnkMceNG8eaNWs455xzNvTOysrK2uy5Zuf7OWjQIAYPHkzbtm05//zzmT17Nvfccw+ffPIJ77333ibLAf/++286dOjAcccdR/fu3Zk4cSJXXnkljRo1omPHjtv93kuSpAIqLkmSlM/GjRsXB+KffPLJVm9z7LHHxlNTU+M//PDDhssWLlwYL1OmTLxVq1YbLmvcuHG8c+fOW32cv//+Ow7Ehw0btkMZZ86cGQfiF110UbZu//zzz8eB+JAhQza5/IQTTojHYrH4999/v+EyIJ6WlhafO3fuhsvGjBkTB+JVq1aNL1u2bMPlV111VRzY5LaHH354HIgPHz58w2Vr166NN2nSJF65cuX4unXr4vF4PJ6RkRFfu3btJnn+/vvveJUqVeJnnHHGhsvmzp0bB+Jly5aNL168eJPbr79u3LhxG+6/ve/n4sWL46mpqfGjjjoqnpmZueHykSNHxoH4gw8+uNlzefjhhzd5LlWrVo0ff/zxWx1DkiQVfC7fkyRJCSczM5PJkydz7LHHsueee264vFq1avTs2ZN3332XZcuWAVC+fHm++uor5syZs8XHKlGiBKmpqUydOpW///472xnWP/6Wlu1tyaRJk0hOTqZ///6bXH7ppZcSj8d55ZVXNrm8TZs2m8zSWj/z6/jjj99kzPWX//jjj5vcPyUlhXPPPXfD+dTUVM4991wWL17Mp59+CkBycjKpqakAZGVl8ddff5GRkcEBBxzAZ599ttlzOP7446lUqdI2n2d2vp+vv/4669at4+KLLyYpaePbzbPPPpuyZcvy8ssvb3L70qVLc+qpp27yXJo3b77Zc5YkSYWLRSlJkpRwfv/9d1atWsVee+212XUNGzYkKyuLBQsWAHD99dezZMkS6tevT6NGjbj88sv54osvNtw+LS2NW265hVdeeYUqVarQqlUrbr31Vn799ddtZihbtiwAy5cvz1bmn376id12222zIlbDhg03XP9P/14SWK5cOQBq1qy5xcv/XQDabbfdKFWq1CaX1a9fH2CT3k4PPfQQ++2334Z+W5UqVeLll19m6dKlmz2H2rVrb/M5Qva+n+uf67///VJTU9lzzz03+17UqFGDWCy2yWW77LLLDhURJUlSwWNRSpIkFWitWrXihx9+4MEHH2Tfffdl7NixNGvWjLFjx264zcUXX8x3333H0KFDKV68OAMGDKBhw4Z8/vnnW33cunXrkpKSsqH5eG5LTk7eocvj/2qWnh2PPvooffr0oU6dOjzwwAO8+uqrTJkyhSOPPHKLvaJKlCiRrcfNyfdzW3LzOUuSpILDopQkSUo4lSpVomTJksyePXuz67799luSkpI2mVFUoUIFTj/9dJ544gkWLFjAfvvtx6BBgza5X506dbj00kuZPHkys2bNYt26dQwfPnyrGUqWLMmRRx7JO++8s2FW1rbUqlWLhQsXbjaz6ttvv91wfW5auHAhK1eu3OSy7777DmDDssCJEyey55578uyzz9KrVy/at29P27ZtWbNmzU6Pv63v5/rn+u9/v3Xr1jF37txc/15IkqSCyaKUJElKOMnJyRx11FG88MILmyxF++2333j88cc59NBDNyyv+/PPPze5b+nSpalbty5r164Fwi56/y7C1KlThzJlymy4zdZcd911xONxevXqxYoVKza7/tNPP+Whhx4CoFOnTmRmZjJy5MhNbnPHHXcQi8VyfRe5jIwMxowZs+H8unXrGDNmDJUqVWL//fcHNs5A+ueMo48++ogPPvggx+Nm5/vZtm1bUlNTufvuuzcZ+4EHHmDp0qV07tw5x+NLkqTCIyXqAJIkqeh68MEHefXVVze7/KKLLmLIkCFMmTKFQw89lL59+5KSksKYMWNYu3Ytt95664bb7r333rRu3Zr999+fChUqMH36dCZOnMgFF1wAhNlDbdq0oXv37uy9996kpKTw3HPP8dtvv3HyySdvM98hhxzCqFGj6Nu3Lw0aNKBXr17Uq1eP5cuXM3XqVF588UWGDBkCwNFHH80RRxzBNddcw7x582jcuDGTJ0/mhRde4OKLL6ZOnTq5+J0LPaVuueUW5s2bR/369ZkwYQIzZszgvvvuo1ixYgB06dKFZ599lm7dutG5c2fmzp3Lvffey957773FIlt2ZOf7WalSJa666ioGDx5Mhw4d6Nq1K7Nnz2b06NEceOCBmzQ1lyRJRZdFKUmSFJl77rlni5f36dOHffbZh2nTpnHVVVcxdOhQsrKyOOigg3j00Uc37EgH0L9/f1588UUmT57M2rVrqVWrFkOGDOHyyy8HQuPwHj168MYbb/DII4+QkpJCgwYNeOqppzj++OO3m/Hcc8/lwAMPZPjw4Tz88MP8/vvvlC5dmmbNmjFu3LgNBZakpCRefPFFBg4cyIQJExg3bhx77LEHw4YN49JLL82F79amdtllFx566CEuvPBC7r//fqpUqcLIkSM5++yzN9ymT58+/Prrr4wZM4bXXnuNvffem0cffZSnn36aqVOn5mjc7H4/Bw0aRKVKlRg5ciSXXHIJFSpU4JxzzuGmm27aUDSTJElFWyxuB0lJkqQCpXXr1vzxxx/MmjUr6iiSJEk5Zk8pSZIkSZIk5TuLUpIkSZIkScp3FqUkSZIkSZKU7+wpJUmSJEmSpHznTClJkiRJkiTlO4tSkiRJkiRJyncpUQfYGVlZWSxcuJAyZcoQi8WijiNJkiRJklTkxeNxli9fzm677UZS0tbnQxXootTChQupWbNm1DEkSZIkSZL0LwsWLKBGjRpbvb5AF6XKlCkDhCdZtmzZiNPsvPT0dCZPnsxRRx1FsWLFoo4jKZ/5GiAVbb4GSEWbrwGSCtPrwLJly6hZs+aGus3WFOii1Pole2XLli00RamSJUtStmzZAv8DKGnH+RogFW2+BkhFm68Bkgrj68D2Wi3Z6FySJEmSJEn5zqKUJEmSJEmS8p1FKUmSJEmSJOW7At1TSpIkSZIk5b7MzEzS09OjjlGkpKenk5KSwpo1a8jMzIw6zjYVK1aM5OTknX4ci1KSJEmSJAmAeDzOr7/+ypIlS6KOUuTE43GqVq3KggULttsgPBGUL1+eqlWr7lRWi1KSJEmSJAlgQ0GqcuXKlCxZskAURwqLrKwsVqxYQenSpUlKStxuS/F4nFWrVrF48WIAqlWrluPHsiglSZIkSZLIzMzcUJCqWLFi1HGKnKysLNatW0fx4sUTuigFUKJECQAWL15M5cqVc7yUL7GfpSRJkiRJyhfre0iVLFky4iQqCNb/nOxM7zGLUpIkSZIkaQOX7Ck7cuPnxKKUJEmSJEmS8p1FKUmSJEmSVCTEYjGef/75XHms8ePHU758+W3eZtCgQTRp0iRXxiuMLEpJkiRJkqQCq0+fPsRiMWKxGMWKFaNKlSq0a9eOBx98kKysrE1uu2jRIjp27BhR0pzp06cPxx57bNQx8oRFKUmSJEmSVKB16NCBRYsWMW/ePF555RWOOOIILrroIrp06UJGRsaG21WtWpW0tLQIk+qfLEpJkiRJkqQCLS0tjapVq1K9enWaNWvG1VdfzQsvvMArr7zC+PHjN9zun8v31q1bxwUXXEC1atUoXrw4tWrVYujQoRtue/vtt9OoUSNKlSpFzZo16du3LytWrNhs7Oeff5569epRvHhx2rdvz4IFC7aZdezYsTRs2JDixYvToEEDRo8evVPP/e2336Z58+akpaVRrVo1/vvf/25SiJs4cSKNGjWiRIkSVKxYkbZt27Jy5UoApk6dSvPmzSlVqhTly5enZcuW/PTTTzuVZ0ek5NtIkiRJkiSpwIjHYdWqaMYuWRJ2dnO3I488ksaNG/Pss89y1llnbXb93XffzYsvvshTTz3F7rvvzoIFCzYpKCUlJXH33XdTu3ZtfvzxR/r27csVV1yxSRFp1apV3HjjjTz88MOkpqbSt29fTj75ZN57770tZnrssccYOHAgI0eOpGnTpnz++eecffbZlCpVil69eu3wc/zll1/o1KkTffr04eGHH+bbb7/l7LPPpnjx4gwaNIhFixbRo0cPbr31Vrp168by5cuZNm0a8XicjIwMjj32WM4++2yeeOIJ1q1bx8cff5yvuy9alJIkSZIkSZtZtQpKl45m7BUroFSpnX+cBg0a8MUXX2zxuvnz51OvXj0OPfRQYrEYtWrV2uT6iy++eMPxHnvswZAhQzjvvPM2KUqlp6czcuRIDjroIAAeeughGjZsyMcff0zz5s03G/O6665j+PDhHHfccQDUrl2br7/+mjFjxuSoKDV69Ghq1qzJyJEjicViNGjQgIULF3LllVcycOBAFi1aREZGBscdd9yG59eoUSMA/vrrL5YuXUqXLl2oU6cOAA0bNtzhDDvD5XuSJEmSJKlQisfjW53506dPH2bMmMFee+1F//79mTx58ibXv/7667Rp04bq1atTpkwZevXqxZ9//smqf0wfS0lJ4cADD9xwvkGDBpQvX55vvvlms/FWrlzJDz/8wJlnnknp0qU3fA0ZMoQffvghR8/vm2++oUWLFps8x5YtW7JixQp+/vlnGjduTJs2bWjUqBEnnngi999/P3///TcAFSpUoE+fPrRv356jjz6au+66i0WLFuUoR05ZlJIkSZIkSZspWTLMWIriq2TJ3HkO33zzDbVr197idc2aNWPu3LnccMMNrF69mu7du3PCCScAMG/ePLp06cJ+++3HM888w6effsqoUaOA0IsqJ9b3o7r//vuZMWPGhq9Zs2bx4Ycf5ugxtyc5OZkpU6bwyiuvsPfeezNixAj22msv5s6dC8C4ceP44IMPOOSQQ5gwYQL169fPsyxb4vI9SZIkSZK0mVgsd5bQReXNN9/kyy+/5JJLLtnqbcqWLctJJ53ESSedxAknnECHDh3466+/+PTTT8nKymL48OEkJYX5PE899dRm98/IyGD69OkblurNnj2bJUuWbHEZXJUqVdhtt9348ccfOeWUUza7Pisra4efY8OGDXnmmWc2mRH23nvvUaZMGWrUqAGE5u4tW7akZcuWDBw4kFq1avHcc8/xn//8B4CmTZvStGlTrrrqKlq0aMHjjz/OwQcfvMNZcsKilCRJkiRJKtDWrl3Lr7/+SmZmJr/99huvvvoqQ4cOpUuXLvTu3XuL97n99tupVq0aTZs2JSkpiaeffpqqVatSvnx56tatS3p6OiNGjODoo4/mvffe4957793sMYoVK8aFF17I3XffTUpKChdccAEHH3zwFvtJAQwePJj+/ftTrlw5OnTowNq1a5k+fTp///33Jj2s/m3p0qXMmDFjk8sqVqxI3759ufPOO7nwwgu54IILmD17Ntdddx3/+c9/SEpK4qOPPuKNN97gqKOOonLlynz00Uf8/vvvNGzYkLlz53LffffRtWtXdtttN2bPns2cOXO2+v3KCxalJEmSJElSgfbqq69SrVo1UlJS2GWXXWjcuDF33303p5122oaZTv9WpkwZbr31VubMmUNycjIHHnggkyZNIikpicaNG3P77bdzyy23cNVVV9GqVSuGDh26WcGmZMmSXHnllfTs2ZNffvmFww47jAceeGCrOc866yxKlizJsGHDuPzyyylVqhSNGjXaZkEKYOrUqTRt2nSTy84880zGjh3LpEmTuPzyy2ncuDEVKlTgzDPP5NprrwXCTLB33nmHO++8k2XLllGrVi2GDx9Ox44d+e233/j222956KGH+PPPP6lWrRr9+vXj3HPPzcZ3PHfE4vF4PN9Gy2XLli2jXLlyLF26lLJly0YdZ6elp6czadIkOnXqRLFixaKOIymf+RogFRFz5sDy5ZtdnJ6VxaRffqFT9eoU29Kb5zJloF69fAgoKQq+D1AiWLNmDXPnzqV27doUL1486jhFTlZWFsuWLaNs2bJbLaQlkm39vGS3XuNMKUmSpPwyZw7Ur7/l60qUgCeegFatYPXqLd/mu+8sTEmSpEIj8UtvkiRJhcUWZkjl6/0lSZISiEUpSZIkSZIk5TuLUpIkSZIkScp3FqUkSZIkSZKU7yxKSZIkSZIkKd9ZlJIkSUoASRkZ1J8wAeLxqKNIkiTlC4tSkiRJCSA5PZ2GTzxBLCsr6iiSJEn5wqKUJElSAknKyIg6giRJUr6wKCVJkpQAMlNSAJwpJUmSigyLUpIkSQkgq1gxfm/UiFjUQSRJKmD69OlDLBbb7KtDhw5RR9tg6tSpxGIxlixZstOP1bp1ay6++OKdfpxEkBJ1AEmSJAVzO3ak0pdfRh1DkqQCp0OHDowbN26Ty9LS0iJKo+xyppQkSVKC+PWgg8hK8u2ZJEk7Ki0tjapVq27ytcsuuwBhllJqairTpk3bcPtbb72VypUr89tvvwGwYMECunfvTvny5alQoQLHHHMM8+bN22SMBx98kH322Ye0tDSqVavGBRdcAMC8efOIxWLMmDFjw22XLFlCLBZj6tSpzJs3jyOOOAKAXXbZhVgsRp8+fQDIyspi6NCh1K5dm1KlSnHooYcyceLEnfpePPPMMxty7rHHHgwfPnyT60ePHk29evUoXrw4VapU4YQTTthw3cSJE2nUqBElSpSgYsWKtG3blpUrV+5Unm1xppQkSVJ+2U6/qHhyMplpaSStXp1PgSRJyoZtFSWSk6F48ezdNikJSpTY/m1LldqxfNuxfrlbr169mDlzJj/++CMDBgzg6aefpkqVKqSnp9O+fXtatGjBtGnTSElJYciQIXTo0IEvvviC1NRU7rnnHv7zn/9w880307FjR5YuXcp7772XrfFr1qzJM888w/HHH8/s2bMpW7YsJf7/+zB06FAeffRR7r33XurUqcPkyZPp3bs3VapU4fDDD9/h5/rpp5/SvXt3Bg0axEknncT7779P3759qVixIn369GH69On079+fRx55hEMOOYS//vprQ7Fu0aJF9OjRg1tvvZVu3bqxfPlypk2bRjwe3+Ec2WVRSpIkKb/cc8/O3b9MmdzJIUnSjihdeuvXdeoEL7+88XzlyrBq1ZZve/jhMHXqxvN77AF//LH57XJQBHnppZco/a+cV199NVdffTUAQ4YMYcqUKZxzzjnMmjWL0047ja5duwIwYcIEsrKyGDt2LLFY6O44btw4ypcvz9SpUznqqKMYMmQIl156KRdddNGGxz/wwAOzlS05OZkKFSoAULlyZcqXLw/A2rVruemmm3j99ddp0aIFWVlZ9OzZk08//ZQxY8bkqCh1++2306ZNGwYMGABA/fr1+frrrxk2bBh9+vRh/vz5lCpVii5dulCmTBlq1apF06ZNgVCUysjI4LjjjqNWrVoANGrUaIcz7AiLUpIkSflh5Eh48MFwPGQIdOy46fVZWfDLL/DOO5CRAa+/Ht6o9+4dri9TBurVy9/MkiQVEEcccQT3/OvDn/WFIIDU1FQee+wx9ttvP2rVqsUdd9yx4bqZM2fy/fffU+ZfH/6sWbOGH374gcWLF7Nw4ULatGmTq5m///57Vq1aRbt27Ta5fN26dRsKRTvqm2++4ZhjjtnkspYtW3LnnXeSmZlJu3btqFWrFnvuuScdOnSgQ4cOdOvWjZIlS9K4cWPatGlDo0aNaN++PUcddRQnnHDChmWQecGilCRJUl77+We49NJwfOON8P+f2m4iPT0UpRo3ho8+ggEDwhKH666DPHwzKEnSdq1YsfXrkpM3Pb948dZv++++if/q2bQzSpUqRd26dbd5m/fffx+Av/76i7/++otS/79McMWKFey///489thjm92nUqVKJG2n3+P66/+5zC09PX27mVf8//f15Zdfpnr16mRlZbFixQpKly69YXlfbitTpgyfffYZU6dOZfLkyQwcOJBBgwbxySefUL58eaZMmcL777/P5MmTGTFiBNdccw0fffQRtWvXzpM8dtKUJEnKazVqwPPPw8UXw1VXbf/2LVtCo0awejU89FBep5MkadtKldr61z/7SW3vtv8utGztdnnghx9+4JJLLuH+++/noIMO4rTTTiPr/3s9NmvWjDlz5lC5cmXq1q27yVe5cuUoU6YMe+yxB2+88cYWH7tSpUpAWP623j+bnkOYqQWQmZm54bK9996btLQ05s+fv2G8Pffck7p161KzZs0cPc+GDRtu1uvqvffeo379+iT/fwExJSWFtm3bcuutt/LFF18wb9483nzzTQBisRgtW7Zk8ODBfP7556SmpvLcc8/lKEt2OFNKkiQpP3TsuPmSva2JxaBvXzj/fBg9Gvr33/zTZUmStMHatWv59ddfN7ksJSWFXXfdlczMTE499VTat2/P6aefTocOHWjUqBHDhw/n8ssv55RTTmHYsGEcc8wxXH/99dSoUYOffvqJZ599liuuuIIaNWowaNAgzjvvPCpXrkzHjh1Zvnw57733HhdeeCElSpTg4IMP5uabb6Z27dosXryYa6+9dpMstWrVIhaL8dJLL9GpUydKlChBmTJluOyyy7jkkkvIysrikEMOYeHChcycOZNy5cpx2mmnbfX5/v7775sVvqpVq8all17KgQceyA033MBJJ53EBx98wMiRIxk9ejQQem/9+OOPtGrVil122YVJkyaRlZXFXnvtxUcffcQbb7zBUUcdReXKlfnoo4/4/fffadiwYe78I21JvABbunRpHIgvXbo06ii5Yt26dfHnn38+vm7duqijSIqArwFSIfPnn/F4ly7x+Jw52br5Zq8By5fH42XLxuMQj7/2Wh4GlZQIfB+gRLB69er4119/HV+9enXUUXbIaaedFgc2+9prr73i8Xg8Pnjw4Hi1atXif/zxx4b7PPPMM/HU1NT4jBkz4vF4PL5o0aJ4796947vuums8LS0tvueee8bPPvvsTeoN9957b3yvvfaKFytWLF6tWrX4hRdeuOG6r7/+Ot6iRYt4iRIl4k2aNIlPnjw5DsTfeuutDbe5/vrr41WrVo3HYrH4aaedFo/H4/GsrKz4nXfeueFxd9111/hRRx0Vf/vtt7f6fA8//PAtPt8bbrghHo/H4xMnTozvvffe8WLFisV33333+LBhwzbcd9q0afHDDz88vssuu8RLlCgR32+//eITJkzY8Bzat28fr1SpUjwtLS1ev379+IgRI7aaY1s/L9mt18Ti8Tzc2y+PLVu2jHLlyrF06VLKli0bdZydlp6ezqRJk+jUqRPFihWLOo6kfOZrgFSIrFkDRx0F06ZBs2YwfXqY/bQNW3wN6N8fRoyArl3hhRfyIbikqPg+QIlgzZo1zJ07l9q1a1P838vylOeysrJYtmwZZcuW3W4fq0SwrZ+X7NZrEv9Z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DjrooGLXHXTQQc6UkiRJlZKNziVJkspBzZo1OeaYYzjmmGMYMGAAF198MYMGDeL8888v8fw1a9YwZMgQTj311BLvJUmSVNU4U0qSJCkGdt99d7KysgBITk4mNze32PNdunTh22+/pV27dpv9JCQUfmT79NNPi1336aef0rFjx/J/AZIkSVHmTClJkqQoWrZsGb169eLCCy+kU6dOpKamMnXqVO655x66d+8OhB34Jk6cSLdu3UhJSaFhw4YMHDiQk08+mdatW/N///d/JCQkMHPmTGbNmsXw4cM33v+f//wn++23H3/605945plnmDx5Mo8//ni8Xq4kSVKZ2ehckiQpirKzsxk8eDDvvvsu8+bNIycnh1atWtGrVy9uueUWatWqxeuvv07//v354YcfaNGiBT/88AMAEyZMYOjQoUyfPp3k5GR22203Lr74Yvr06QOERucZGRm88sorfPjhhzRr1oy7776b0047LY6vWJIkqWwMpSRJkiqJknbtkyRJqqzsKSVJkiRJkqSYM5SSJEmSJElSzNnoXJIkqZKw64IkSapKnCklSZIkSZKkmDOUkiRJkiRJUswZSkmSJEmSJCnmDKUkSZIkSZIUc4ZSkiRJkiRJijlDKUmSJEmSJMWcoZQkSZIkSZJizlBKkiRJkiRJMWcoJUmSJEmSpJj7f5vXg8+Tc1BzAAAAAElFTkSuQmCC",
      "text/plain": [
       "<Figure size 1200x800 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "plt.figure(figsize=(10, 6))\n",
    "steps = np.arange(len(losses_disable))\n",
    "\n",
    "plt.plot(steps, losses_disable, label=\"Disable Loss\", color=\"blue\")\n",
    "plt.plot(steps, losses_execute, label=\"Execute Loss\", color=\"red\", linestyle=\"--\")\n",
    "\n",
    "plt.scatter(steps, losses_disable, color=\"blue\", s=100)\n",
    "plt.scatter(steps, losses_execute, color=\"red\", marker=\"s\", s=100)\n",
    "\n",
    "plt.xlabel(\"Step\")\n",
    "plt.ylabel(\"Loss\")\n",
    "plt.title(\"Loss Comparison\")\n",
    "plt.legend()\n",
    "plt.grid()\n",
    "plt.xticks(steps)\n",
    "\n",
    "plt.tight_layout()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f8b91fdf",
   "metadata": {},
   "source": [
    "## Full Training\n",
    "\n",
    "After validating the model's FHE behavior, the quantized model is trained in clear on the filtered dataset to validate convergence."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "de1f17f9",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "2025-03-26 10:29:58,595 - INFO - === Starting new training session ===\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "LoRA layers detected in the model.\n"
     ]
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "b51c37381caf487289d38ef11832161c",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Compiling FHE layers:   0%|          | 0/221 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "2025-03-26 10:31:43,299 - INFO - Compilation complete.\n"
     ]
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "ea274fd551864e668bd5413f71488be4",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Epoch 1:   0%|          | 0/1479 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "2025-03-26 10:35:42,918 - INFO - Step 100: loss=0.335604, avg_loss=0.592850\n",
      "2025-03-26 10:35:42,962 - INFO - Average gradient magnitude: 0.244334\n",
      "2025-03-26 10:35:42,963 - INFO - Running evaluation at step 100...\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Prompt: When you multiply a number by 7, it becomes 98. What is that number?\n",
      "\n",
      "Response: If you multiply a number by 7, it becomes 98. So, the number you're asking about is 98.\n",
      "\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "2025-03-26 10:36:44,321 - INFO - [Evaluation at step 100] perplexity: 1.4078\n",
      "2025-03-26 10:40:45,460 - INFO - Step 200: loss=0.247193, avg_loss=0.450941\n",
      "2025-03-26 10:40:45,501 - INFO - Average gradient magnitude: 0.335301\n",
      "2025-03-26 10:40:45,502 - INFO - Running evaluation at step 200...\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Prompt: When you multiply a number by 7, it becomes 98. What is that number?\n",
      "\n",
      "Response: The number that 98 is multiplied by is 7. So, the number is 98 * 7 = 702.\n",
      "\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "2025-03-26 10:41:44,560 - INFO - [Evaluation at step 200] perplexity: 1.4067\n",
      "2025-03-26 10:45:56,970 - INFO - Step 300: loss=0.079890, avg_loss=0.444334\n",
      "2025-03-26 10:45:57,016 - INFO - Average gradient magnitude: 0.091397\n",
      "2025-03-26 10:45:57,017 - INFO - Running evaluation at step 300...\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Prompt: When you multiply a number by 7, it becomes 98. What is that number?\n",
      "\n",
      "Response: If you multiply 7 by 98, you get 98 * 7 = 702.\n",
      "\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "2025-03-26 10:46:49,066 - INFO - [Evaluation at step 300] perplexity: 1.3947\n",
      "2025-03-26 10:51:04,550 - INFO - Step 400: loss=0.016669, avg_loss=0.420956\n",
      "2025-03-26 10:51:04,598 - INFO - Average gradient magnitude: 0.034923\n",
      "2025-03-26 10:51:04,598 - INFO - Running evaluation at step 400...\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Prompt: When you multiply a number by 7, it becomes 98. What is that number?\n",
      "\n",
      "Response: The number 98 is the answer to the question.\n",
      "\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "2025-03-26 10:51:50,182 - INFO - [Evaluation at step 400] perplexity: 1.3715\n",
      "2025-03-26 10:55:48,938 - INFO - Step 500: loss=0.268339, avg_loss=0.405399\n",
      "2025-03-26 10:55:48,981 - INFO - Average gradient magnitude: 0.268719\n",
      "2025-03-26 10:55:48,982 - INFO - Running evaluation at step 500...\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Prompt: When you multiply a number by 7, it becomes 98. What is that number?\n",
      "\n",
      "Response: If you multiply 7 by 98, you get 98 * 7 = 672.\n",
      "\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "2025-03-26 10:56:41,760 - INFO - [Evaluation at step 500] perplexity: 1.3689\n",
      "2025-03-26 11:00:47,376 - INFO - Step 600: loss=0.061164, avg_loss=0.389228\n",
      "2025-03-26 11:00:47,424 - INFO - Average gradient magnitude: 0.094080\n",
      "2025-03-26 11:00:47,424 - INFO - Running evaluation at step 600...\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Prompt: When you multiply a number by 7, it becomes 98. What is that number?\n",
      "\n",
      "Response: The number that becomes 98 when you multiply 7 by itself is 49.\n",
      "\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "2025-03-26 11:01:37,540 - INFO - [Evaluation at step 600] perplexity: 1.3429\n",
      "2025-03-26 11:05:34,106 - INFO - Step 700: loss=0.049174, avg_loss=0.372756\n",
      "2025-03-26 11:05:34,148 - INFO - Average gradient magnitude: 0.076843\n",
      "2025-03-26 11:05:34,149 - INFO - Running evaluation at step 700...\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Prompt: When you multiply a number by 7, it becomes 98. What is that number?\n",
      "\n",
      "Response: The number that is being multiplied by 7 is 98.\n",
      "\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "2025-03-26 11:06:18,797 - INFO - [Evaluation at step 700] perplexity: 1.3076\n",
      "2025-03-26 11:10:16,397 - INFO - Step 800: loss=0.114913, avg_loss=0.366715\n",
      "2025-03-26 11:10:16,440 - INFO - Average gradient magnitude: 0.201189\n",
      "2025-03-26 11:10:16,441 - INFO - Running evaluation at step 800...\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Prompt: When you multiply a number by 7, it becomes 98. What is that number?\n",
      "\n",
      "Response: To find the number, you need to divide 98 by 7. \n",
      "\n",
      "98 ÷ 7 = 14\n",
      "\n",
      "So the number is\n",
      "\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "2025-03-26 11:11:14,821 - INFO - [Evaluation at step 800] perplexity: 1.2973\n",
      "2025-03-26 11:15:14,813 - INFO - Step 900: loss=0.126561, avg_loss=0.354234\n",
      "2025-03-26 11:15:14,867 - INFO - Average gradient magnitude: 0.104785\n",
      "2025-03-26 11:15:14,867 - INFO - Running evaluation at step 900...\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Prompt: When you multiply a number by 7, it becomes 98. What is that number?\n",
      "\n",
      "Response: To find the number, you need to divide 98 by 7. \n",
      "\n",
      "98 ÷ 7 = 14\n",
      "\n",
      "So, the number is\n",
      "\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "2025-03-26 11:16:16,442 - INFO - [Evaluation at step 900] perplexity: 1.2808\n",
      "2025-03-26 11:20:13,539 - INFO - Step 1000: loss=0.344479, avg_loss=0.344842\n",
      "2025-03-26 11:20:13,582 - INFO - Average gradient magnitude: 0.255919\n",
      "2025-03-26 11:20:13,583 - INFO - Running evaluation at step 1000...\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Prompt: When you multiply a number by 7, it becomes 98. What is that number?\n",
      "\n",
      "Response: To find the number, you need to divide 98 by 7.\n",
      "\n",
      "98 ÷ 7 = 14\n",
      "\n",
      "So the number is 14\n",
      "\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "2025-03-26 11:21:16,761 - INFO - [Evaluation at step 1000] perplexity: 1.2511\n",
      "2025-03-26 11:25:15,729 - INFO - Step 1100: loss=0.354381, avg_loss=0.335532\n",
      "2025-03-26 11:25:15,771 - INFO - Average gradient magnitude: 0.290001\n",
      "2025-03-26 11:25:15,771 - INFO - Running evaluation at step 1100...\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Prompt: When you multiply a number by 7, it becomes 98. What is that number?\n",
      "\n",
      "Response: To find the number, you need to divide 98 by 7.\n",
      "\n",
      "98 ÷ 7 = 14\n",
      "\n",
      "So the number is 14\n",
      "\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "2025-03-26 11:26:15,104 - INFO - [Evaluation at step 1100] perplexity: 1.2496\n",
      "2025-03-26 11:30:04,476 - INFO - Step 1200: loss=0.130505, avg_loss=0.326929\n",
      "2025-03-26 11:30:04,518 - INFO - Average gradient magnitude: 0.197031\n",
      "2025-03-26 11:30:04,518 - INFO - Running evaluation at step 1200...\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Prompt: When you multiply a number by 7, it becomes 98. What is that number?\n",
      "\n",
      "Response: To find the number, you need to divide 98 by 7.\n",
      "\n",
      "98 ÷ 7 = 14\n",
      "\n",
      "So the number is 14\n",
      "\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "2025-03-26 11:31:01,618 - INFO - [Evaluation at step 1200] perplexity: 1.2336\n",
      "2025-03-26 11:34:59,935 - INFO - Step 1300: loss=0.782768, avg_loss=0.323717\n",
      "2025-03-26 11:34:59,978 - INFO - Average gradient magnitude: 0.438046\n",
      "2025-03-26 11:34:59,979 - INFO - Running evaluation at step 1300...\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Prompt: When you multiply a number by 7, it becomes 98. What is that number?\n",
      "\n",
      "Response: To find the number, you need to divide 98 by 7. \n",
      "\n",
      "98 ÷ 7 = 14\n",
      "\n",
      "So the number is\n",
      "\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "2025-03-26 11:35:59,463 - INFO - [Evaluation at step 1300] perplexity: 1.2246\n",
      "2025-03-26 11:39:52,631 - INFO - Step 1400: loss=0.323173, avg_loss=0.316051\n",
      "2025-03-26 11:39:52,673 - INFO - Average gradient magnitude: 0.343263\n",
      "2025-03-26 11:39:52,673 - INFO - Running evaluation at step 1400...\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Prompt: When you multiply a number by 7, it becomes 98. What is that number?\n",
      "\n",
      "Response: To find the number, you need to divide 98 by 7. \n",
      "\n",
      "98 ÷ 7 = 14\n",
      "\n",
      "So the number is\n",
      "\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "2025-03-26 11:40:52,313 - INFO - [Evaluation at step 1400] perplexity: 1.2274\n",
      "2025-03-26 11:43:57,682 - INFO - Epoch 1 completed. Avg Loss: 0.311628, FHE Mode: disable\n",
      "2025-03-26 11:43:57,682 - INFO - Training completed. Final Avg Loss: 0.311628, FHE Mode: disable\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "Evaluating final model after full training...\n",
      "Prompt: When you multiply a number by 7, it becomes 98. What is that number?\n",
      "\n",
      "Response: To find the number, you need to divide 98 by 7. \n",
      "\n",
      "98 ÷ 7 = 14\n",
      "\n",
      "So the number is\n",
      "\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Final perplexity after extended training: 1.22\n",
      "Improvement from initial model: 35.84\n"
     ]
    }
   ],
   "source": [
    "# Fully train the model in disable mode\n",
    "model_final = initialize_model()\n",
    "trainer_final, full_train_dl = setup_trainer(model_final, logging_steps=100)\n",
    "\n",
    "trainer_final.train(full_train_dl, fhe=\"disable\", device=DEVICE)\n",
    "\n",
    "# Final evaluation\n",
    "print(\"\\nEvaluating final model after full training...\")\n",
    "model_final.eval()\n",
    "metrics_final = metric_fn(model_final, eval_dl)\n",
    "print(f\"Final perplexity after extended training: {metrics_final['perplexity']:.2f}\")\n",
    "print(\n",
    "    f\"Improvement from initial model: \"\n",
    "    f\"{pre_training_metrics['perplexity'] - metrics_final['perplexity']:.2f}\"\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ba2a111c",
   "metadata": {},
   "source": [
    "## Save the Model\n",
    "\n",
    "Save the fine-tuned model to disk."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "5d5fbcf5",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "2025-03-26 11:54:06,388 - INFO - Model saved at deployment/llama_lora_finetuned_7bit\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Model saved to: deployment/llama_lora_finetuned_7bit\n"
     ]
    }
   ],
   "source": [
    "if SAVE_PATH.is_dir() and any(SAVE_PATH.iterdir()):\n",
    "    shutil.rmtree(SAVE_PATH)\n",
    "trainer_final.save_and_clear_private_info(SAVE_PATH)\n",
    "print(\"Model saved to:\", SAVE_PATH)"
   ]
  }
 ],
 "metadata": {
  "execution": {
   "timeout": 10800
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
